/*
 * -----------------------------------------------------------------------------
 * Project  : Oracle OCCI Support
 * Purpose  : Support Oracle OCCI connectivity.
 * File     : Win32.Data.OracleOcci.h
 * Author   : Eduardo Sobrino
 * Date     : Jul/23/2007
 * Remarks  : Mar/04/2011 Make sure to link your project to the
 *          : D:\oracle\product\10.2.0\client_2\oci\lib\msvc\vc8\oraocci10.lib
 *          : Oracle client library when you will needed the OCCI resources.
 */

#pragma region   // Include Files

#include "stdafx.h"
#include "Win32.Data.OracleOcci.h"

#pragma endregion

#ifdef SUPPORT_ORACLE_OCCI_

// -----------------------------------------------------------------------------

namespace Kif
{
namespace Win32
{
namespace Data
{
namespace OracleOcciClient
{

// -----------------------------------------------------------------------------
#pragma region // Define OdbcParameter

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Define OdbcDatabase

/// <summary>Initialize the database environment</summary>
OcciDatabase::OcciDatabase()
{
   pDatabaseConnection = NULL;
   //pStatement = NULL;

   Verbose = Kif::Win32::Diagnostics::Verbose_None;

   pEnvironment = oracle::occi::Environment::
      createEnvironment();  // oracle::occi::Environment::DEFAULT);
   Available = pEnvironment != NULL;
   Connected = false ;

   ConnectionTimeout = -1 ;
   CommandTimeout    = -1 ;

   // query database capabilities...
   if (Available)
   {
   }
}  // end of OcciDatabase (ctor)

/// <sumarry>Set error based on exception info</summary>
/// <param name="exception">SQL exception</param>
/// <date>Jul/23/2007  (ESob)</date>
void OcciDatabase::SetError(std::exception &exception)
{
   LastReturnCode = OcciError_Failed;
   LastErrorMessage.Copy((char*)exception.what());

   if (Verbose == Kif::Win32::Diagnostics::Verbose_Trace)
   {
      if (LastErrorMessage.GetLength() > 0)
      {
         Kif::Win32::String mess;

         mess.Copy(LastErrorMessage.GetBuffer(),LastErrorMessage.GetLength());

         Kif::Win32::Diagnostics::Log::WriteMessage(mess.GetBuffer()) ;
      }
   }

   // get error code ...
   DatabaseErrorCode = OcciError_Failed;
}  // end of get error based on handle type

/// <sumarry>Set error based on exception info</summary>
/// <param name="exception">SQL exception</param>
/// <date>Jul/23/2007  (ESob)</date>
void OcciDatabase::SetError(oracle::occi::SQLException &exception)
{
   LastReturnCode = exception.getErrorCode();
   LastErrorMessage.Copy((char*)exception.what());

   if (Verbose == Kif::Win32::Diagnostics::Verbose_Trace)
   {
      if (LastErrorMessage.GetLength() > 0)
      {
         Kif::Win32::String mess;

         mess.Copy(LastErrorMessage.GetBuffer(),LastErrorMessage.GetLength());

         Kif::Win32::Diagnostics::Log::WriteMessage(mess.GetBuffer()) ;
      }
   }

   // get error code ...
   DatabaseErrorCode = OcciError_Failed;
}  // end of get error based on handle type

/// <sumarry>Get error string. The length of the message string is returned
/// </summary>
/// <param name=""></param>
/// <date>Jul/23/2007  (ESob)</date>
int OcciDatabase::GetError(Kif::Win32::String &errorMessage)
{
   long messlen = 0;
   if (LastReturnCode != OcciError_SUCCESS)
      errorMessage.Copy(LastErrorMessage);
   return(messlen) ;
}  // end of get error

/// <summary>Release all ODBC allocated resources...</summary>
/// <date>Jul/23/2007  (ESob)</date>
void OcciDatabase::Dispose()
{
   ResetError();
   if (Available)
   {
      // free OCCI environment handle, if any...
      if (pEnvironment != NULL)
         oracle::occi::Environment::terminateEnvironment(pEnvironment);
   }

   Available = false ;
}  // end of release all OCCI allocated resources...

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Define OcciConnection

/// <summary>Set default connection timeout</summary>
/// <param name="lNewTimeout">new timeout time in seconds (long)</param>
/// <date>Jul/23/2007  (ESob)</date>
/// <returns>true is returned if done</returns>
bool OcciConnection::SetConnectionTimeout(long timeout)
{
   bool done = true;
   Database.ConnectionTimeout = timeout ;

   // SQLSetConnectAttr SQL_LOGIN_TIMEOUT
   if ((timeout != -1) && (Database.pDatabaseConnection != NULL))
   {
      //SQLRETURN retcode = SQLSetConnectOption( // SQLSetConnectAttr(
      //   Database.hDatabaseConnection, SQL_LOGIN_TIMEOUT, timeout) ; //, 0) ;
      //done = (retcode == SQL_SUCCESS) || (retcode == SQL_SUCCESS_WITH_INFO) ;

      //// if there is a mesage display it ...
      //if (retcode != SQL_SUCCESS)
      //{
      //   Database.GetError(SQL_HANDLE_DBC,Database.hDatabaseConnection) ;
      //}
   }

   return(done) ;
}  // end of set connection timeout

/// <summary>Set default command timeout</summary>
/// <param name="lNewTimeout">new timeout time in seconds (long)</param>
/// <date>Jul/23/2007  (ESob)</date>
/// <returns>true is returned if done</returns>
bool OcciConnection::SetCommandTimeout(long timeout)
{  bool done = true;
   Database.CommandTimeout = timeout ;
   return(done) ;
}  // end of set command timeout

/// <sumarry>Connect to database using given data source info</summary>
/// <param name="dataSource">data source info to connect to</param>
/// <returns>true is returned if connected</returns>
/// <date>Jul/23/2007  (ESob)</date>
bool OcciConnection::Connect(DataSourceInfo &dataSource)
{
   Database.ResetError();
   Kif::Win32::String errMess;
   DataSource.Copy(dataSource);
   Database.LastReturnCode = OcciError_SUCCESS;

   if (!Database.Available)
   {
      if (Database.pDatabaseConnection != NULL)
      {
         Database.pEnvironment->
            terminateConnection(Database.pDatabaseConnection);
         Database.pDatabaseConnection = NULL;
         Database.LastReturnCode = OcciError_SUCCESS;
         Database.Available = (Database.LastReturnCode == OcciError_SUCCESS) ;
      }
      else
         Database.Available = true ;
   }

   if (Database.Available)
   {
      Kif::Win32::AnsiString uid(dataSource.UserId.GetBuffer(),
         dataSource.UserId.GetLength());
      Kif::Win32::AnsiString pwd(dataSource.Password.GetBuffer(),
         dataSource.Password.GetLength());
      Kif::Win32::AnsiString connString(dataSource.SourceName.GetBuffer(),
         dataSource.SourceName.GetLength());

      const std::string userId   = uid.GetBuffer();
      const std::string password = pwd.GetBuffer();
      const std::string cs = connString.GetBuffer();

      try
      {
         Database.pDatabaseConnection =
            Database.pEnvironment->createConnection(userId, password, cs);

         if (Database.pDatabaseConnection == NULL)
            Database.LastReturnCode = OcciError_FATALERROR_FAILTOCONNECT;
         else
            Database.LastReturnCode = OcciError_SUCCESS;
      }
      catch (oracle::occi::SQLException &ex)
      {
         Database.SetError(ex);
      }
      catch (std::exception &ex)
      {
         Database.SetError(ex);
      }

      Database.Connected = Database.Success() ;
   }
   else
   {
      Kif::Win32::String ostr;

      TCHAR couldnt[] = _T("Could not connect: <not available> Alias(");
      TCHAR uid[]     = _T(") uid(");
      TCHAR end[]     = _T(")");

      ostr.Copy(couldnt,SizeOf(couldnt));
      ostr.Concat(dataSource.SourceName);
      ostr.Concat(uid,SizeOf(uid));
      ostr.Concat(dataSource.UserId);
      ostr.Concat(end,SizeOf(end));

      Kif::Win32::Diagnostics::Log::WriteMessage(ostr.GetBuffer());
   }

   return(Database.Connected) ;
}  // end of Connect

/// <sumarry>Disconnect from the database</summary>
/// <date>Nov/98  (ESob)</date>
void OcciConnection::Disconnect()
{
   if (Database.Available && Database.Connected)
   {
      // disconnect from the database, and
      // free OCCI connection handle, if any...
      if (Database.pDatabaseConnection != NULL)
      {
         Database.pEnvironment->
            terminateConnection(Database.pDatabaseConnection);
         Database.pDatabaseConnection = NULL ;
         Database.Available = false ;
      }
   }

   Database.Connected = false ;
}  // end of disconnect...

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Define OdbcCommand

/// <summary>Bind fields</summary>
/// <param name="pStatement">handle to statement</param>
/// <param name="columns">columsn to bind</param>
/// <date>Nov/98  (ESob)</date>
/// <returns>returns true if fields where binded</returns>
bool OcciCommand::BindColumns(
   oracle::occi::Statement *pStatement, OcciColumnsCollection &columns)
{
   DWORD count;
   bool done;
   OcciColumn *col;

   for (count = 0; count < columns.Count; count++)
   {
      col = columns.getItem(count);
      switch (col->GetType())
      {
         case Kif::Win32::BaseType_AnsiString:
            break ;

         case Kif::Win32::BaseType_WideString:
            break ;

         case Kif::Win32::BaseType_Int32:
            break ;

         case Kif::Win32::BaseType_Double:
            break ;

         case Kif::Win32::BaseType_DateTime:
            break;

         default :;
      }
   }

   done = true ;
   return(done) ;
}  // end of BindColumns

/// <summary>Bind statement parameters</summary>
/// <param name="pStatement">handle to statement</param>
/// <param name="parameters">parameters to bind</param>
/// <date>Jan/98  (ESob)</date>
/// <returns>true is returned if params where binded</returns>
bool OcciCommand::BindParameters(
   oracle::occi::Statement *pStatement, OcciParametersCollection &parameters)
{
   DWORD count;
   unsigned int indx = 0;
   bool done = true;
   OcciParameter *param;
   Kif::Win32::AnsiString ansiParam;

   // BIND to statement parameters
   for (count = 0; count < parameters.Count; count++)
   {
      indx++;
      param = parameters.getItem(count);
      switch(param->GetType())
      {
         case Kif::Win32::BaseType_WideString:
            {
               ansiParam.Copy(param->AsWideString(),param->GetSize());
               const std::string pval = ansiParam.GetBuffer();
               pStatement->setString(indx,pval);
            }
            break;

         case Kif::Win32::BaseType_AnsiString:
            {
               ansiParam.Copy(param->AsWideString(),param->GetSize());
               const std::string pval = ansiParam.GetBuffer();
               pStatement->setString(indx,pval);
            }
            break;

         case Kif::Win32::BaseType_Double:
            {
               double *d = (double*)param->getValue();
               pStatement->setDouble(indx,*d);
            }
            break;

         case Kif::Win32::BaseType_Int16:
            {
               short *s = (short*)param->getValue();
               pStatement->setInt(indx,*s);
            }
            break;

         case Kif::Win32::BaseType_Int32:
            {
               int *i = (int*)param->getValue();
               pStatement->setInt(indx,*i);
            }
            break;

         case Kif::Win32::BaseType_DateTime:
            break;

         case Kif::Win32::BaseType_Blob:
            break;
      }
   }

   return(done) ;
}  // end of BindParameters

/// <summary>Given a result status from an execute statement check set
/// corresponding OcciCommand lastReturnCode</summary>
/// <param name="status">given status to check</param>
/// <date>Aug/01 (ESob)</date>
/// <returns>return the setted lastReturnCode</returns>
long OcciCommand::checkStatus(oracle::occi::Statement::Status status)
{
   lastStatus = status;
   switch(status)
   {
      case oracle::occi::Statement::Status::UNPREPARED:
         Connection->Database.LastReturnCode = OcciError_Failed;
         break;
      case oracle::occi::Statement::Status::PREPARED:
         Connection->Database.LastReturnCode = OcciError_SUCCESS;
         break;
      case oracle::occi::Statement::Status::RESULT_SET_AVAILABLE:
         Connection->Database.LastReturnCode = OcciError_SUCCESS;
         break;
      case oracle::occi::Statement::Status::UPDATE_COUNT_AVAILABLE:
         Connection->Database.LastReturnCode = OcciError_SUCCESS;
         break;
      case oracle::occi::Statement::Status::NEEDS_STREAM_DATA:
         Connection->Database.LastReturnCode = OcciError_SUCCESS;
         break;
      case oracle::occi::Statement::Status::STREAM_DATA_AVAILABLE:
         Connection->Database.LastReturnCode = OcciError_SUCCESS;
         break;
   }
   return Connection->Database.LastReturnCode;
}  // end of CheckStatus

/// <summary>Create a new statement handle</summary>
/// <param name="sqlStatementBuffer">sql statement to execute</param>
/// <param name="statementBufferLength">statement buffer length</param>
/// <date>Nov/98  (ESob)</date>
/// <returns>true is returned if succeded</returns>
bool OcciCommand::createStatement(
   TCHAR *sqlStatementBuffer, size_t statementBufferLength)
{
   bool done = false;
   if (Connection)
   {
      Kif::Win32::AnsiString stmt(sqlStatementBuffer,
         statementBufferLength);
      const std::string sqlStmt = stmt.GetBuffer();

      // if there is an existing stmt/query reuse it, else create a new one
      if (!pStatement)
         pStatement =
            Connection->Database.pDatabaseConnection->
               createStatement(sqlStmt);
      else
         pStatement->setSQL(sqlStmt);

      if (!pStatement)
         Connection->Database.LastReturnCode =
            OcciError_FailedToCreateStatement;
      else
         Connection->Database.LastReturnCode = OcciError_SUCCESS;

      done = Connection->Database.Success() ;
   }
   return(done) ;
}  // end of CreateStatement

/// <summary>Execute a non-query</summary>
/// <param name="commandText">command text</param>
/// <date>Nov/98  (ESob)</date>
/// <returns>true is returned if succeded</returns>
bool OcciCommand::ExecuteNonQuery(Kif::Win32::String &commandText)
{
   bool done;
   if (Connection)
   {
      done = createStatement(commandText.GetBuffer(),commandText.GetLength());
      if (done)
      {
         if (Parameters.Count > 0)
            done = BindParameters(pStatement,Parameters);

         // if all is ok then bind columns...
         if (done)
         {
            checkStatus(pStatement->execute());
            done = Connection->Database.Success();
            if (!done)
            {
               Kif::Win32::String err;
               Connection->Database.GetError(err);
               // TODO: do something with error message...
            }
         }
      }
   }
   else
      done = false;

   return(done);
}  // end of ExecuteNonQuery

/// <summary>Execute a prepared command</summary>
/// <date>Nov/98  (ESob)</date>
/// <returns>true is returned if succeded</returns>
bool OcciCommand::ExecutePreparedCommand()
{
   bool done;
   if (!Connection)
      return(false);
   checkStatus(pStatement->execute());
   done = Connection->Database.Success() ;
   if (!done)
   {
      Kif::Win32::String err;
      Connection->Database.GetError(err);
      // TODO: do something with error message...
   }

   return(done) ;
}  // end of ExecutePreparedCommand

/// <summary>Prepare non-query command</summary>
/// <param name="commandText">command text</param>
/// <date>Nov/98 (ESob)</date>
/// <returns>true is returned if succeded</returns>
bool OcciCommand::prepareQuery(TCHAR *commandText)
{
   bool done;
   createStatement(commandText,Kif::Win32::String::GetLength(commandText));
   done = Connection->Database.Success();
   if (!done)
   {
      Kif::Win32::String err;
      Connection->Database.GetError(err);
      // TODO: do something with error message...
   }

   return(done);
}  // end of prepare statement

/// <summary>Prepare non-query command</summary>
/// <param name="commandText">command text</param>
/// <date>Nov/98 (ESob)</date>
/// <returns>true is returned if succeded</returns>
bool OcciCommand::Prepare(TCHAR *commandText)
{
   bool done;
   if (pStatement)
   {
      createStatement(commandText,Kif::Win32::String::GetLength(commandText));
      done = Connection->Database.Success();
      if (!done)
      {
         Kif::Win32::String err;
         Connection->Database.GetError(err);
         // TODO: do something with error message...
      }
   }
   else
      done = false;

   return(done);
}  // end of prepare statement

/// <summary>Prepare query</summary>
/// <param name="commandText">command text</param>
/// <param name="columns">columns to bind too</param>
/// <date>Nov/98  (ESob)</date>
/// <returns>true is returned if succeded</returns>
bool OcciCommand::Prepare(TCHAR *commandText, OcciColumnsCollection &columns)
{
   bool done;
   if (!Connection)
      return(false);

   done = prepareQuery(commandText);
   if (done)
   {
      done = BindColumns(pStatement,columns);
      if (done)
      {
         pResultSet = pStatement->executeQuery();
         if (pResultSet)
            Connection->Database.LastReturnCode = OcciError_SUCCESS;
         else
            Connection->Database.LastReturnCode = OcciError_Failed;
      }
   }

   return(done) ;
}  // end of prepare statement

/// <summary>Prepare query</summary>
/// <param name="commandText">command text</param>
/// <param name="columns">columns to bind too</param>
/// <date>Nov/98  (ESob)</date>
/// <returns>true is returned if succeded</returns>
OcciDataReader *OcciCommand::OpenReader(
   TCHAR *commandText, OcciColumnsCollection &columns)
{
   OcciDataReader *reader = NULL;
   bool done;
   if (!Connection)
      return(reader);

   // execute query
   done = Prepare(commandText,columns);
   if (done)
      reader = new OcciDataReader(this);

   return(reader) ;
}  // end of prepare statement

/// <summary>Prepare query</summary>
/// <param name="commandText">command text</param>
/// <param name="params">params to bind too</param>
/// <param name="columns">columns to bind too</param>
/// <date>Nov/98  (ESob)</date>
/// <returns>true is returned if succeded</returns>
OcciDataReader *OcciCommand::OpenReader(
   TCHAR *commandText,
   OcciParametersCollection &params, OcciColumnsCollection &columns)
{
   OcciDataReader *reader = NULL;
   bool done;
   if (!Connection)
      return(reader);

   done = createStatement(
      commandText, Kif::Win32::String::GetLength(commandText));

   // if all is ok then bind columns...
   if (done)
   {
      // bind params
      if (params.Count > 0)
      {
         done = BindParameters(pStatement,params) ;
         if (!done)
         {
            Kif::Win32::String err;
            Connection->Database.GetError(err);
            // TODO: do something with error message...
         }
      }

      // bind columns
      if (columns.Count > 0)
         done = BindColumns(pStatement,columns) ;

      // execute query
      pResultSet = pStatement->executeQuery();
      if (pResultSet)
         Connection->Database.LastReturnCode = OcciError_SUCCESS;
      else
         Connection->Database.LastReturnCode = OcciError_Failed;
      done = Connection->Database.Success() ;

      if (done)
         reader = new OcciDataReader(this);
      else
      {
         Kif::Win32::String err;
         Connection->Database.GetError(err);
         // TODO: do something with error message...
      }
   }

   return(reader) ;
}  // end of prepare statement

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Define OdbcDataReader class

/// <summary>Move to next available record</summary>
/// <date>Nov/98  (ESob)</date>
/// <returns>true is returned if succeded</returns>
bool OcciDataReader::Read()
{
   oracle::occi::ResultSet::Status sts = pResultSet->next();
   switch(sts)
   {
      case oracle::occi::ResultSet::Status::DATA_AVAILABLE:
         command->Connection->Database.LastReturnCode = OcciError_SUCCESS;
         break;
      case oracle::occi::ResultSet::Status::END_OF_FETCH:
         command->Connection->Database.LastReturnCode =
            OcciError_NoMoreDataAvailable;
         break;
      case oracle::occi::ResultSet::Status::STREAM_DATA_AVAILABLE:
         command->Connection->Database.LastReturnCode = OcciError_SUCCESS;
         break;
   }

   if (Success())
   {
      eof = false;
      bof = false;
   }
   else
   {
      eof = true;
      bof = true;
   }

   return(!eof) ;
}  // end of Next

/// <summary>Get data as string</summary>
/// <date>Nov/98  (ESob)</date>
/// <returns>pointer to a String is returned</returns>
Kif::Win32::String *OcciDataReader::AsString(
   DWORD index, Kif::Win32::String &column)
{
   column.Dispose();
   std::string s = pResultSet->getString(index);
   if (s.size() > 0)
      column.Copy((char*)s.c_str(),s.size());

   return &column;
}  // end of AsString

#pragma endregion
// -----------------------------------------------------------------------------

}  // end of Kif::Win32::Data::OracleOcciClient
}  // end of Kif::Win32::Data
}  // end of Kif::Win32
}  // end of Kif

#endif
